/**==============================================================================
            Copyright (c) 2013 Moritz Hader, Marcel Schrder
            All Rights Reserved.

	@file  ARBrowser.java

	@brief This Actvity capsualtes the access to the ElementManager. It provides
	methods and properties to receive and process objects present within the Information
	System. For now, objects are requested every 500 ms and not interpolated.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
==============================================================================*/

package de.ibm.dhbw;

import java.util.ArrayList;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;

import android.content.Intent;
import android.provider.Settings.System;


import de.ibm.dhbw.backing.dataHolding.DynamicElementObject;
import de.ibm.dhbw.backing.dataHolding.ElementManager;
import de.ibm.dhbw.backing.dataHolding.ElementObject;
import de.ibm.dhbw.backing.dataHolding.MoeMessageObject;
import de.ibm.dhbw.heimdall.HeimdallAR;
import de.ibm.dhbw.heimdall.engine.Heimdall3DObject;
import de.ibm.dhbw.heimdall.engine.HeimdallAbstractElement;
import de.ibm.dhbw.heimdall.engine.HeimdallNode;
import de.ibm.dhbw.heimdall.engine.HeimdallSurface;
import de.ibm.dhbw.heimdall.engine.HeimdallMesh;
import de.ibm.dhbw.heimdall.engine.math.HVector3D;

public class ARBrowser extends HeimdallAR{
	private static Logger Log = Logger.getLogger(ARBrowser.class.getCanonicalName());
	
	//Some local testing data
	private Heimdall3DObject obj2;
	
	//Backing-Object for the InformationSystem
	private ElementManager em;
	private MoeMessageObject mmo;
	private float timePerFrame = 0.0f;
	private String IPAdress;
	
		//Everything we'll need for multithreading the backing
		//private Handler backingHandler;
		private Timer backingTimer;
		private DataBackingRunnable backingRunnable;
		private Object mmoLock = new Object();
		private Boolean hasChanged;
	
	//Local node
	private HeimdallNode node;
	
	//Testing
	private Random rand;
	private ArrayList<ElementObject> elObjChanged;
	
	
	@Override
	public void create(){

		/*
		 * We create the handler within the create (formerly onCreate) method, as handler-instanciation 
		 * is not permitted within an asynchronus-task
		 */
		
		this.backingRunnable = new DataBackingRunnable();
		this.backingTimer = new Timer();
		
		this.rand = new Random(java.lang.System.currentTimeMillis());
		
		this.elObjChanged = new ArrayList<ElementObject>();
		
		Intent intent = getIntent();
		this.IPAdress = (String) intent.getStringExtra("ISIPADRESS");
	}
	
	@Override
	public void destroy(){
		this.backingTimer.cancel();
	}
	
	@Override
	public void initSystem(){
		Log.log(Level.INFO, "Calling the initialize-method inherited by HeimdallAR");
		
		HeimdallMesh mesh2 = new HeimdallMesh();
			mesh2.load(HeimdallMesh.STOCK_MESHES.DEFAULT_GRID);
		
		HeimdallSurface color2 = new HeimdallSurface(0.0f, 1.0f, 0.0f, 1.0f);
		
		obj2 = new Heimdall3DObject(mesh2, color2);
		obj2.setInfoID(-12345);
		obj2.setMarkerID(1);
		obj2.setLocScale(new HVector3D(10.0f, 10.0f, 10.0f));
		
		
		//Test
		/*HeimdallMesh meshCone = new HeimdallMesh();
			meshCone.load(HeimdallMesh.STOCK_MESHES.DEFAULT_FANCYCAR);
			
		HeimdallSurface colorCone = new HeimdallSurface(0.75f, 0.75f, 0.75f, 1.0f);
		
		Heimdall3DObject coneObj = new Heimdall3DObject(meshCone, colorCone);
		
		coneObj.setLocScale(new HVector3D(40.0f, 40.0f, 40.0f));
		coneObj.setMarkerID(1);
		coneObj.setInfoID(-12345);
		coneObj.setLocPosition(new HVector3D(0.0f, 0.0f, 0.0f));*/
		
			
		node = new HeimdallNode();
			//node.attachElement(obj);
			//node.attachElement(obj2); node.attachElement(coneObj);
			//node.attachElement(obj3);
		//node.setLocScale(new HVector3D(4.0f, 4.0f, 4.0f));
		
		this.attachObject(node);
		
		//Information System backing
		//this.em = new ElementManager("149.172.235.116", "8080", 0.5f);
		this.em = new ElementManager(this.IPAdress, "8080", 0.5f);
		
		this.hasChanged = false;
		//Thread thread = new Thread(backingRunnable);
		//thread.start();
		this.backingTimer.schedule(backingRunnable, 500, 500);
		//this.backingTimer;
	}
	
	/*********************************************************************************************
	 * BEGIN: Custom functions for local laboratory-testing
	 *********************************************************************************************/
	
	private void fetchLatestObjects(){
		
		//So far, I dont know any better method to transform the integer-list into an int array.
		//Its still the better tradeoff than requesting the markers one after the other
		int[] intArray = this.getMarkerIDs();
		
	
			if(intArray == null || intArray.length == 0){
				return;
			}
		

			//Log.info("IntArr len"+intArray.length);
		
		this.setMessageObject(this.em.getElements(intArray));
		
		//Indikate, that the message object has been updated
		this.hasChanged = true;
	}
	
	/**
	 * This method processes the current mmo (MoeMessageObject) and checks the
	 * local node for changes, additions or deletions.
	 */
	private void processLatestObjects(){
		
		if(this.getMessageObject() != null){
			
				//Process any object, that may have been added since the last iteration
				for(int i=0;i<this.getMessageObject().getNewElements().size();i++){
					
					//1) Get the Element-Object. We will need this one to access position, markerID etc.
					ElementObject elObj = this.getMessageObject().getNewElements().get(i);
					
					//2) Prepare Mesh and Surface for the new 3DObject
					Heimdall3DObject HDobj = this.generate3DObject(elObj);
						
					//4) Last but not least, add the new Object to our local Node
						this.node.attachElement(HDobj);
				}
			
			//Check if there is any element on the root-node that may have changed or been removed.
			this.searchNodeForISObject(this.root, 8);
			
		}
	}
	
	private Heimdall3DObject generate3DObject(ElementObject elObj){
		
		HeimdallMesh hdMesh = new HeimdallMesh();
		HeimdallSurface hdColor;
		Heimdall3DObject hdObj = new Heimdall3DObject();
			
			switch(elObj.getType()){
				case 100:
						hdMesh.load(HeimdallMesh.STOCK_MESHES.DEFAULT_TRAFFICL);
						hdColor = new HeimdallSurface(0.25f,0.25f, 0.25f, 1.0f);
						
						hdObj.setLocScale(new HVector3D(5.0f,5.0f,5.0f));
					break;
					
				case 101:
						hdMesh.load(HeimdallMesh.STOCK_MESHES.DEFAULT_BARRIER);
						hdColor = new HeimdallSurface(1.0f,0.0f, 0.0f, 1.0f);
						
						hdObj.setLocScale(new HVector3D(5.0f,5.0f,5.0f));
					break;
					
				case 102:
						hdMesh.load(HeimdallMesh.STOCK_MESHES.DEFAULT_CONE);
						hdColor = new HeimdallSurface(1.0f,0.65f, 0.0f, 1.0f);
						
						hdObj.setLocScale(new HVector3D(5.0f,5.0f,10.0f));
						
					break;
				default:
						hdMesh.load(HeimdallMesh.STOCK_MESHES.DEFAULT_CUBE);
						hdColor = new HeimdallSurface(rand.nextFloat(),rand.nextFloat(), rand.nextFloat(), 1.0f);
						
						hdObj.setLocScale(new HVector3D(5.0f,5.0f,5.0f));
					break;
			}
			
			hdObj.setMesh(hdMesh);
			hdObj.setSurface(hdColor);
			
			hdObj.setLocPosition(new HVector3D(elObj.getARB_posX()*1000, elObj.getARB_posY()*1000, 0.0f));
			
			hdObj.setInfoID(elObj.getId());
			hdObj.setMarkerID(elObj.getMarker().getId());
			
			

			if(elObj instanceof DynamicElementObject){
				DynamicElementObject dynElObj = (DynamicElementObject)elObj;
				//Log.info("Dynamic Object DirX:"+dynElObj.getDirectionX()+" DirY: "+dynElObj.getDirectionY());
				hdObj.setLocRotation(new HVector3D(0.0f, 0.0f, dynElObj.getDirectionInDegrees()));
			}
		
		return hdObj;
	}
	
	/**
	 * This method will perform a recursive depth-first search throughout all levels of the given Node.
	 * Thereby it will only descend up to the given depth to prevent an infinite search.
	 * NOTE: This method may need to be refactored to meet future performance requirements.
	 * @param list
	 * @param currentNode
	 * @param maxDepth
	 */
	private void searchNodeForISObject(HeimdallNode currentNode, int maxDepth){
			
			
			for(HeimdallAbstractElement elem: currentNode.getLocNode()){
				
				//If the element is an instance of Node, we'll have to descend further into the tree
				if((elem instanceof HeimdallNode) == true){
					
					//Only descend, if the maxdepth was not reached yet
					if(maxDepth > 0){
						searchNodeForISObject((HeimdallNode)elem, maxDepth-1);
					}
					
					continue;
				}
				
				/*
				 * We now will iterate over the lists within the MoeMessageObject to check for deleted or altered elements.
				 * This is done in here, because searching the lists and doing a depth-search for every element of the list is more costly
				 * than doing one depth-search and several (linear) list-searches.
				 */
				
				//Process any Object that may need to be deleted by now
				for(int i =0;i<this.getMessageObject().getDiscardingElementIds().size();i++){
					
					//1) Get the corresponding ID
					int toDelete = this.getMessageObject().getDiscardingElementIds().get(i).intValue();
					
					//2) Compare the id to the current element to see if it needs deletion
					if(toDelete == elem.getInfoID()){
						Log.info("Detaching element form the root-Node");
						
						this.detachObject(elem);
					}
				}
				
				
				//Process any Object that may have changed since the last iteration
				
				for(int j = 0; j<this.getMessageObject().getChangedElements().size();j++){
					
					//1) Get the corresponding element
					ElementObject chgElem = this.getMessageObject().getChangedElements().get(j);
					
						//1.1) TESTCODE: Check the recent-list if any duplicates appeared
						/*if(this.elementIsDuplicate(chgElem)){
							continue;
						}*/
					
					
					//2) Compare the id to the current element to see if it needs to be updated
					
					if(chgElem.getId() == elem.getInfoID()){
						Log.info("Updating element "+chgElem.getId()+" to position x: "+chgElem.getARB_posX()*1000+" y:"+ chgElem.getARB_posY()*1000);
						
						elem.setLocPosition(new HVector3D(chgElem.getARB_posX()*1000, chgElem.getARB_posY()*1000, 0.0f));
						
						//Rotation
						
						if(chgElem instanceof DynamicElementObject){
							DynamicElementObject dynElObj = (DynamicElementObject)chgElem;
							 //Log.info("Dynamic Object DirX:"+dynElObj.getDirectionX()+" DirY: "+dynElObj.getDirectionY());
							elem.setLocRotation(new HVector3D(0.0f, 0.0f, dynElObj.getDirectionInDegrees()));
						}
						
					}
				}
				
				
			}
			
			//TESTCODE: Update the recent list of elements
			/*this.elObjChanged.clear();
			
			for(int j = 0; j<this.getMessageObject().getChangedElements().size();j++){
				this.elObjChanged.add(this.getMessageObject().getChangedElements().get(j));
			}*/
		
	}
	
	/**
	 * This Method iterates over the local Node and tries to update every object
	 * with the new, interpolated location.
	 * NOTE: This method unfortunately traverses the Node a second time. So far, it
	 * is not possible to implement this functionality into the other method.
	 * @param currentNode
	 * @param maxDepth
	 */
	public void interpolateObjects(HeimdallNode currentNode, int maxDepth){
		
		
		for(HeimdallAbstractElement elem: currentNode.getLocNode()){	
			
			//If the element is an instance of Node, we'll have to descend further into the tree
			if((elem instanceof HeimdallNode) == true){
				
				//Only descend, if the maxdepth was not reached yet
				if(maxDepth > 0){
					interpolateObjects((HeimdallNode)elem, maxDepth-1);
				}
				
				continue;
			}
			
			//Additional to the normal processing of received data, we will use this method to calculate future-data
			
			ArrayList<ElementObject> interpolatedObj = this.getMessageObject().getInterpolatedElements(this.timePerFrame*10);//this.timePerFrame*500
			
			
			//Process any Object that may need to be interpolated by now
			for(int i =0;i<interpolatedObj.size();i++){
				
				//1) Get the corresponding element
				ElementObject chgElem = interpolatedObj.get(i);
				
				
				//2) Compare the id to the curremnt element to see if it needs to be updated
				if(chgElem.getId() == elem.getInfoID()){
					//Log.info("Updating interpol element "+chgElem.getId()+" to position x: "+chgElem.getARB_posX()*1000+" y:"+ chgElem.getARB_posY()*1000);
					
					//elem.setLocPosition(new HVector3D(chgElem.getARB_posX()*1000, chgElem.getARB_posY()*1000, 0.0f));
					
				}
			}
			
			
		}
	}
	
	public boolean elementIsDuplicate(ElementObject chgElem){
		
		for(int k = 0; k<this.elObjChanged.size();k++){
			
			ElementObject histObj = this.elObjChanged.get(k);
			
			if(histObj.getId() == chgElem.getId()){
				
				if(histObj.getARB_posX() == chgElem.getARB_posX() && histObj.getARB_posY() == chgElem.getARB_posY()){
					//Skip this object
					return(true);
				}
			}
		}
		
		return(false);
	}
	
	public class DataBackingRunnable extends TimerTask{
		
		@Override
		public void run() {
			fetchLatestObjects();
			
		}
		
	}
	/*********************************************************************************************
	 * END: Custom functions for local laboratory-testing
	 *********************************************************************************************/
	

	
	
	@Override
	public void update(float tpf){
		//Log.log(Level.INFO, "Calling the uppdate-method inherited by HeimdallAR. TPF: "+tpf);
		
		
		//Processing must happen here in the update-loop, as access to the render-objects is only
		//valid within the render-update-loop
		if(this.hasChanged){
			
			this.processLatestObjects();
			this.hasChanged = false; //TODO:What if a lost-update happens? Must check that.
		}else{
		
			//Additional to the processing, we will need to interpolate the movement of the objects while 
			//no update is in place.
			if(this.getMessageObject() != null){
				this.timePerFrame = tpf; //Don't like this style. Must suffice for now
				//this.interpolateObjects(this.root, 8);
			}
		}
	}

	private MoeMessageObject getMessageObject() {
		synchronized (mmoLock) {
			return mmo;
		}
	}

	private void setMessageObject(MoeMessageObject mmo) {
		synchronized (mmoLock) {
			this.mmo = mmo;	
		}
	}
}
